/*
 * prabsyn.c - Print Abstract Syntax data structures. Most functions 
 *           handle an instance of an abstract syntax rule.
 */

#include <stdio.h>
#include "util.h"
#include "symbol.h" /* symbol table data structures */
#include "absyn.h"  /* abstract syntax data structures */
#include "prabsyn.h" /* function prototype */

char res[MAX_LENGTH];

/* local function prototypes */
void chstr(string org);
static void pr_var(FILE *out, A_var v, int d);
static void pr_dec(FILE *out, A_dec v, int d);
static void pr_ty(FILE *out, A_ty v, int d);
static void pr_field(FILE *out, A_field v, int d);
static void pr_fieldList(FILE *out, A_fieldList v, int d);
static void pr_expList(FILE *out, A_expList v, int d);
static void pr_fundec(FILE *out, A_fundec v, int d);
static void pr_fundecList(FILE *out, A_fundecList v, int d);
static void pr_decList(FILE *out, A_decList v, int d);
static void pr_namety(FILE *out, A_namety v, int d);
static void pr_nametyList(FILE *out, A_nametyList v, int d);
static void pr_efield(FILE *out, A_efield v, int d);
static void pr_efieldList(FILE *out, A_efieldList v, int d);

static void indent(FILE *out, int d) {
	int i;
	for (i = 0; i <= d; i++) fprintf(out, " ");
}

/* Print A_var types. Indent d spaces. */
static void pr_var(FILE *out, A_var v, int d) {
	indent(out, d);
	switch (v->kind) {
		case A_simpleVar:
			fprintf(out, "simpleVar(%s)", S_name(v->u.simple)); 
			break;
		case A_fieldVar:
			fprintf(out, "%s\n", "fieldVar(");
			pr_var(out, v->u.field.var, d+1); fprintf(out, "%s\n", ","); 
			indent(out, d+1); fprintf(out, "%s)", S_name(v->u.field.sym));
			break;
		case A_subscriptVar:
			fprintf(out, "%s\n", "subscriptVar(");
			pr_var(out, v->u.subscript.var, d+1); fprintf(out, "%s\n", ","); 
			pr_exp(out, v->u.subscript.exp, d+1); fprintf(out, "%s", ")");
			break;
		default:
			assert(0); 
	} 
}

static char str_oper[][12] = {
	"PLUS", "MINUS", "TIMES", "DIVIDE", 
	"EQUAL", "NOTEQUAL", "LESSTHAN", "LESSEQ", "GREAT", "GREATEQ"};

static void pr_oper(FILE *out, A_oper d) {
	fprintf(out, "%s", str_oper[d]);
}

/* Print A_var types. Indent d spaces. */
void pr_exp(FILE *out, A_exp v, int d) {
	indent(out, d);
	switch (v->kind) {
		case A_varExp:
			fprintf(out, "varExp(\n"); pr_var(out, v->u.var, d+1); 
			fprintf(out, "%s", ")");
			break;
		case A_nilExp:
			fprintf(out, "nilExp()");
			break;
		case A_doubleExp:
			fprintf(out, "doubleExp()");
			break;
		case A_intExp:
			fprintf(out, "intExp(%d)", v->u.intt);
			break;
		case A_stringExp:
			chstr(v->u.stringg);
			fprintf(out, "stringExp(%s)", res);
			break;
		case A_callExp:
			fprintf(out, "callExp(%s,\n", S_name(v->u.call.func));
			pr_expList(out, v->u.call.args, d+1); fprintf(out, ")");
			break;
		case A_opExp:
			fprintf(out, "opExp(\n");
			indent(out, d+1); pr_oper(out, v->u.op.oper); fprintf(out, ",\n"); 
			pr_exp(out, v->u.op.left, d+1); fprintf(out, ",\n"); 
			pr_exp(out, v->u.op.right, d+1); fprintf(out, ")");
			break;
		case A_recordExp:
			fprintf(out, "recordExp(%s,\n", S_name(v->u.record.typ)); 
			pr_efieldList(out, v->u.record.fields, d+1); fprintf(out, ")");
			break;
		case A_seqExp:
			fprintf(out, "seqExp(\n");
			pr_expList(out, v->u.seq, d+1); fprintf(out, ")");
			break;
		case A_assignExp:
			fprintf(out, "assignExp(\n");
			pr_var(out, v->u.assign.var, d+1); fprintf(out, ",\n");
			pr_exp(out, v->u.assign.exp, d+1); fprintf(out, ")");
			break;
		case A_ifExp:
			fprintf(out, "iffExp(\n");
			pr_exp(out, v->u.iff.test, d+1); fprintf(out, ",\n");
			pr_exp(out, v->u.iff.then, d+1);
			if (v->u.iff.elsee) { /* else is optional */
				fprintf(out, ",\n");
				pr_exp(out, v->u.iff.elsee, d+1);
			}
			fprintf(out, ")");
			break;
		case A_whileExp:
			fprintf(out, "whileExp(\n");
			pr_exp(out, v->u.whilee.test, d+1); fprintf(out, ",\n");
			pr_exp(out, v->u.whilee.body, d+1); fprintf(out, ")");
			break;
		case A_forExp:
			fprintf(out, "forExp(%s,\n", S_name(v->u.forr.var)); 
			pr_exp(out, v->u.forr.lo, d+1); fprintf(out, ",\n");
			pr_exp(out, v->u.forr.hi, d+1); fprintf(out, "%s\n", ",");
			pr_exp(out, v->u.forr.body, d+1); fprintf(out, ",\n");
			indent(out, d+1); fprintf(out, "%s", v->u.forr.escape ? "TRUE)" : "FALSE)");
			break;
		case A_breakExp:
			fprintf(out, "breakExp()");
			break;
		case A_letExp:
			fprintf(out, "letExp(\n");
			pr_decList(out, v->u.let.decs, d+1); fprintf(out, ",\n");
			pr_exp(out, v->u.let.body, d+1); fprintf(out, ")");
			break;
		case A_arrayExp:
			fprintf(out, "arrayExp(%s,\n", S_name(v->u.array.typ));
			pr_exp(out, v->u.array.size, d+1); fprintf(out, ",\n");
			pr_exp(out, v->u.array.init, d+1); fprintf(out, ")");
			break;
		default:
			assert(0); 
	} 
}

void chstr(string org){
	int i = 0;
	while(*org){
		if (*org == '\n') {
			res[i++] = '\\';
			res[i++] = 'n';
			org++;
		} else {
			res[i++] = *org++;
		}
	}
	res[i++] = '\0';
}

static void pr_dec(FILE *out, A_dec v, int d) {
	indent(out, d);
	switch (v->kind) {
		case A_functionDec:
			fprintf(out, "functionDec(\n"); 
			pr_fundecList(out, v->u.function, d+1); fprintf(out, ")");
			break;
		case A_varDec:
			fprintf(out, "varDec(%s,\n", S_name(v->u.var.var));
			if (v->u.var.typ) {
				indent(out, d+1); fprintf(out, "%s,\n", S_name(v->u.var.typ)); 
			}
			pr_exp(out, v->u.var.init, d+1); fprintf(out, ",\n");
			indent(out, d+1); fprintf(out, "%s", v->u.var.escape ? "TRUE)" : "FALSE)");
			break;
		case A_typeDec:
			fprintf(out, "typeDec(\n"); 
			pr_nametyList(out, v->u.type, d+1); fprintf(out, ")");
			break;
		default:
			assert(0); 
	} 
}

static void pr_ty(FILE *out, A_ty v, int d) {
	indent(out, d);
	switch (v->kind) {
		case A_nameTy:
			fprintf(out, "nameTy(%s)", S_name(v->u.name));
			break;
		case A_recordTy:
			fprintf(out, "recordTy(\n");
			pr_fieldList(out, v->u.record, d+1); fprintf(out, ")");
			break;
		case A_arrayTy:
			fprintf(out, "arrayTy(%s)", S_name(v->u.array));
			break;
		default:
			assert(0); 
	} 
}

static void pr_field(FILE *out, A_field v, int d) {
	indent(out, d);
	fprintf(out, "field(%s,\n", S_name(v->name));
	indent(out, d+1); fprintf(out, "%s,\n", S_name(v->typ));
	indent(out, d+1); fprintf(out, "%s", v->escape ? "TRUE)" : "FALSE)");
}

static void pr_fieldList(FILE *out, A_fieldList v, int d) {
	indent(out, d);
	if (v) {
		fprintf(out, "fieldList(\n");
		pr_field(out, v->head, d+1); fprintf(out, ",\n");
		pr_fieldList(out, v->tail, d+1); fprintf(out, ")");
	}
	else fprintf(out, "fieldList()");
}

static void pr_expList(FILE *out, A_expList v, int d) {
	indent(out, d);
	if (v) {
		fprintf(out, "expList(\n"); 
		pr_exp(out, v->head, d+1); fprintf(out, ",\n");
		pr_expList(out, v->tail, d+1);
		fprintf(out, ")");
	}
	else fprintf(out, "expList()"); 

}

static void pr_fundec(FILE *out, A_fundec v, int d) {
	indent(out, d);
	fprintf(out, "fundec(%s,\n", S_name(v->name));
	pr_fieldList(out, v->params, d+1); fprintf(out, ",\n");
	if (v->result) {
		indent(out, d+1); fprintf(out, "%s,\n", S_name(v->result));
	}
	pr_exp(out, v->body, d+1); fprintf(out, ")");
}

static void pr_fundecList(FILE *out, A_fundecList v, int d) {
	indent(out, d);
	if (v) {
		fprintf(out, "fundecList(\n"); 
		pr_fundec(out, v->head, d+1); fprintf(out, ",\n");
		pr_fundecList(out, v->tail, d+1); fprintf(out, ")");
	}
	else fprintf(out, "fundecList()");
}

static void pr_decList(FILE *out, A_decList v, int d) {
	indent(out, d);
	if (v) {
		fprintf(out, "decList(\n"); 
		pr_dec(out, v->head, d+1); fprintf(out, ",\n");
		pr_decList(out, v->tail, d+1);
		fprintf(out, ")");
	}
	else fprintf(out, "decList()"); 

}

static void pr_namety(FILE *out, A_namety v, int d) {
	indent(out, d);
	fprintf(out, "namety(%s,\n", S_name(v->name)); 
	pr_ty(out, v->ty, d+1); fprintf(out, ")");
}

static void pr_nametyList(FILE *out, A_nametyList v, int d) {
	indent(out, d);
	if (v) {
		fprintf(out, "nametyList(\n"); 
		pr_namety(out, v->head, d+1); fprintf(out, ",\n");
		pr_nametyList(out, v->tail, d+1); fprintf(out, ")");
	}
	else fprintf(out, "nametyList()");
}

static void pr_efield(FILE *out, A_efield v, int d) {
	indent(out, d);
	if (v) {
		fprintf(out, "efield(%s,\n", S_name(v->name));
		pr_exp(out, v->exp, d+1); fprintf(out, ")");
	}
	else fprintf(out, "efield()");
}

static void pr_efieldList(FILE *out, A_efieldList v, int d) {
	indent(out, d);
	if (v) {
		fprintf(out, "efieldList(\n"); 
		pr_efield(out, v->head, d+1); fprintf(out, ",\n");
		pr_efieldList(out, v->tail, d+1); fprintf(out, ")");
	}
	else fprintf(out, "efieldList()");
}
